﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Windows;
using Rxx.Labs.Properties;

namespace Rxx.Labs.Reactive.Networking
{
	[DisplayName("Sockets (New)")]
	[Description("Establishing a socket connection and transmitting data.")]
	public partial class SocketLab : BaseLab
	{
		public static readonly DependencyProperty IsCommunicatingProperty = DependencyProperty.Register(
			"IsCommunicating",
			typeof(bool),
			typeof(SocketLab),
			new PropertyMetadata(false));

		public bool IsCommunicating
		{
			get
			{
				return (bool) GetValue(IsCommunicatingProperty);
			}
			private set
			{
				SetValue(IsCommunicatingProperty, value);
			}
		}

		public string HostNameOrIPAddress
		{
			get;
			set;
		}

		public int Port
		{
			get;
			set;
		}

		private SerialDisposable subscription;

		public SocketLab()
		{
			HostNameOrIPAddress = "microsoft.com";
			Port = 80;

			InitializeComponent();
		}

		protected override IEnumerable<IDisposable> Main()
		{
			yield return subscription = new SerialDisposable();
		}

		private void GetHttpOptions(object sender, RoutedEventArgs e)
		{
			var host = HostNameOrIPAddress == null ? null : HostNameOrIPAddress.Trim();
			var port = Port;

			if (string.IsNullOrEmpty(host))
			{
				TraceLine(Text.InvalidHostNameOrIPAddress);
				return;
			}

			if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
			{
				TraceLine(Text.InvalidPortOutOfRange);
				return;
			}

			IsCommunicating = true;

			try
			{
				TraceLine(Text.Subscribing);
				TraceLine();

				var message = Encoding.UTF8.GetBytes("OPTIONS * HTTP/1.1\r\nHost: " + host + ":" + port + "\r\n\r\n");

				subscription.Disposable =
					(from socket in ObservableSocket.Connect(
						AddressFamily.InterNetwork,
						SocketType.Stream,
						ProtocolType.Tcp,
						host,
						port)
					 from response in
						 Observable.Using(
							 () => socket,
							 __ => from _ in socket.SendUntilCompleted(message, 0, message.Length)
										 from response in Observable.Using(
											 () => new MemoryStream(),
											 stream => socket
												 .ReceiveUntilCompleted()
												 .Do(buffer => stream.Write(buffer, 0, buffer.Length))
												 .Aggregate(0, (sum, buffer) => sum + buffer.Length)
												 .Select(length => Encoding.UTF8.GetString(stream.ToArray(), 0, length)))
										 select response)
					 select response)
					 .ObserveOnDispatcher()
					 .Finally(() => IsCommunicating = false)
					 .Subscribe(ConsoleOutput);
			}
			catch (Exception ex)
			{
				TraceError(ex);

				IsCommunicating = false;
			}
		}

		private void Cancel(object sender, RoutedEventArgs e)
		{
			TraceLine(Text.Disposing);

			subscription.Disposable = Disposable.Empty;

			TraceLine(Text.Done);
		}
	}
}